package cn.amaake.magicplugin.nacos.util;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

import cn.amaake.magicplugin.nacos.enums.FileTypeEnum;
import com.alibaba.nacos.api.annotation.NacosProperties;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.annotation.NacosIgnore;
import com.alibaba.nacos.api.config.annotation.NacosProperty;
import com.alibaba.nacos.api.exception.NacosException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.BeanExpressionResolver;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.context.expression.EnvironmentAccessor;
import org.springframework.context.expression.StandardBeanExpressionResolver;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertyResolver;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

public abstract class NacosUtils {
    public static final String DEFAULT_STRING_ATTRIBUTE_VALUE = "";
    public static final String DEFAULT_CONFIG_TYPE_VALUE = "properties";
    public static final boolean DEFAULT_BOOLEAN_ATTRIBUTE_VALUE = false;
    public static final String SEPARATOR = "|";
    public static final long DEFAULT_TIMEOUT = Long.getLong("nacos.default.timeout", 5000L);
    private static final Set<Class<?>> NON_BEAN_CLASSES = Collections.unmodifiableSet(new HashSet(Arrays.asList(Object.class, Class.class)));
    private static ExpressionParser parser = new SpelExpressionParser();
    private static BeanExpressionResolver resolver = new StandardBeanExpressionResolver();
    private static ConcurrentHashMap<String, Expression> expressionCache = new ConcurrentHashMap();
    private static ConcurrentHashMap<Environment, StandardEvaluationContext> environmentContextCache = new ConcurrentHashMap();
    private static final Logger logger = LoggerFactory.getLogger(NacosUtils.class);

    public NacosUtils() {
    }

    public static String buildDefaultPropertySourceName(String dataId, String groupId, Map<?, ?> properties) {
        return build(dataId, groupId, identify(properties));
    }

    public static String identify(NacosProperties nacosProperties) {
        return identify(AnnotationUtils.getAnnotationAttributes(nacosProperties));
    }

    public static String identify(Map<?, ?> properties) {
        String namespace = (String) properties.get("namespace");
        String serverAddress = (String) properties.get("serverAddr");
        String contextPath = (String) properties.get("contextPath");
        String clusterName = (String) properties.get("clusterName");
        String endpoint = (String) properties.get("endpoint");
        String accessKey = (String) properties.get("accessKey");
        String secretKey = (String) properties.get("secretKey");
        String encode = (String) properties.get("encode");
        return build(namespace, clusterName, serverAddress, contextPath, endpoint, accessKey, secretKey, encode);
    }

    private static String build(Object... values) {
        StringBuilder stringBuilder = new StringBuilder();
        Object[] var2 = values;
        int var3 = values.length;

        for (int var4 = 0; var4 < var3; ++var4) {
            Object value = var2[var4];
            String stringValue = value == null ? null : String.valueOf(value);
            if (StringUtils.hasText(stringValue)) {
                stringBuilder.append(stringValue);
            }

            stringBuilder.append("|");
        }

        return stringBuilder.toString();
    }

    public static boolean isDefault(final NacosProperties nacosProperties) {
        final List<Object> records = new LinkedList();
        ReflectionUtils.doWithMethods(nacosProperties.annotationType(), new ReflectionUtils.MethodCallback() {
            public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
                if (Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == 0) {
                    Object defaultValue = method.getDefaultValue();
                    if (defaultValue != null) {
                        try {
                            Object returnValue = method.invoke(nacosProperties);
                            if (!defaultValue.equals(returnValue)) {
                                records.add(returnValue);
                            }
                        } catch (Exception var4) {
                        }
                    }
                }

            }
        });
        return records.isEmpty();
    }

    public static String readFromEnvironment(String label, Environment environment) {
        String value = resolvePlaceholders(label, environment);
        return StringUtils.hasText(value) ? evaluate(value, environment) : value;
    }

    public static Object readFromBeanFactory(String label, ConfigurableBeanFactory beanFactory) {
        if (beanFactory == null) {
            return label;
        } else {
            String value = beanFactory.resolveEmbeddedValue(label);
            return StringUtils.hasText(value) ? evaluate(value, beanFactory) : value;
        }
    }

    public static String resolvePlaceholders(String label, Environment environment) {
        return environment == null ? label : environment.resolvePlaceholders(label);
    }

    public static String evaluate(String value, Environment environment) {
        Expression expression = (Expression) expressionCache.get(value);
        if (expression == null) {
            expression = parser.parseExpression(value, new TemplateParserContext());
            expressionCache.put(value, expression);
        }

        StandardEvaluationContext evaluationContext = (StandardEvaluationContext) environmentContextCache.get(environment);
        if (evaluationContext == null) {
            evaluationContext = new StandardEvaluationContext(environment);
            evaluationContext.addPropertyAccessor(new EnvironmentAccessor());
            environmentContextCache.put(environment, evaluationContext);
        }

        return (String) expression.getValue(evaluationContext, String.class);
    }

    public static Object evaluate(String value, ConfigurableBeanFactory beanFactory) {
        return resolver.evaluate(value, new BeanExpressionContext(beanFactory, (Scope) null));
    }

    public static String readFileExtension(String dataId) {
        int lastIndex = dataId.lastIndexOf(".");
        String extName = dataId.substring(lastIndex + 1);
        FileTypeEnum fileTypeEnum = FileTypeEnum.getFileTypeEnumByFileExtensionOrFileType(extName);
        return fileTypeEnum.getFileType();
    }

    public static PropertyValues resolvePropertyValues(Object bean, String content, String type) {
        return resolvePropertyValues(bean, "", "", "", content, type);
    }

    public static PropertyValues resolvePropertyValues(Object bean, final String prefix, String dataId, String groupId, String content, String type) {
        final Map<String, Object> configProperties = toProperties(dataId, groupId, content, type);
        final MutablePropertyValues propertyValues = new MutablePropertyValues();
        ReflectionUtils.doWithFields(bean.getClass(), new ReflectionUtils.FieldCallback() {
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                String propertyName = resolvePropertyName(field);
                propertyName = StringUtils.isEmpty(prefix) ? propertyName : prefix + "." + propertyName;
                if (StringUtils.hasText(propertyName)) {
                    if (Collection.class.isAssignableFrom(field.getType()) || Map.class.isAssignableFrom(field.getType())) {
                        bindContainer(prefix, propertyName, configProperties, propertyValues);
                        return;
                    }

                    if (containsDescendantOf(configProperties.keySet(), propertyName) && !isUnbindableBean(field.getType())) {
                        bindBean(propertyName, field.getType(), configProperties, propertyValues);
                        return;
                    }

                    if (configProperties.containsKey(propertyName)) {
                        String propertyValue = String.valueOf(configProperties.get(propertyName));
                        propertyValues.add(field.getName(), propertyValue);
                    }
                }

            }
        });
        return propertyValues;
    }

    public static Properties resolveProperties(NacosProperties nacosProperties, PropertyResolver propertyResolver) {
        return resolveProperties((NacosProperties) nacosProperties, propertyResolver, (Properties) null);
    }

    public static Properties resolveProperties(NacosProperties nacosProperties, PropertyResolver propertyResolver, Properties defaultProperties) {
        Map<String, Object> attributes = AnnotationUtils.getAnnotationAttributes(nacosProperties);
        return resolveProperties(attributes, propertyResolver, defaultProperties);
    }

    public static Properties resolveProperties(Map<String, Object> attributes, PropertyResolver propertyResolver, Properties defaultProperties) {
        if (CollectionUtils.isEmpty(attributes)) {
            return defaultProperties;
        } else {
            Properties resolveProperties = resolveProperties(attributes, propertyResolver);
            merge(resolveProperties, defaultProperties);
            return resolveProperties;
        }
    }

    public static Properties resolveProperties(Map<?, ?> properties, PropertyResolver propertyResolver) {
        PropertiesPlaceholderResolver propertiesPlaceholderResolver = new PropertiesPlaceholderResolver(propertyResolver);
        return propertiesPlaceholderResolver.resolve(properties);
    }

    protected static void merge(Properties targetProperties, Properties sourceProperties) {
        if (!CollectionUtils.isEmpty(sourceProperties)) {
            Iterator var2 = sourceProperties.entrySet().iterator();

            while (var2.hasNext()) {
                Map.Entry entry = (Map.Entry) var2.next();
                String propertyName = (String) entry.getKey();
                if (!targetProperties.containsKey(propertyName)) {
                    String propertyValue = (String) entry.getValue();
                    targetProperties.setProperty(propertyName, propertyValue);
                }
            }

        }
    }

    public static String getContent(ConfigService configService, String dataId, String groupId) {
        String content = null;

        try {
            content = configService.getConfig(dataId, groupId, DEFAULT_TIMEOUT);
        } catch (NacosException var5) {
            NacosException e = var5;
            if (logger.isErrorEnabled()) {
                logger.error("Can't get content from dataId : " + dataId + " , groupId : " + groupId, e);
            }
        }

        return content;
    }

    private static void bindBean(String propertyName, Class<?> target, Map<String, Object> configProperties, MutablePropertyValues propertyValues) {
        Object propertyValue = configProperties.get(propertyName);
        if (propertyValue != null) {
            propertyValues.add(propertyName, propertyValue);
        }

        if (!isUnbindableBean(target)) {
            Field[] fields = target.getDeclaredFields();
            Field[] var6 = fields;
            int var7 = fields.length;

            for (int var8 = 0; var8 < var7; ++var8) {
                Field field = var6[var8];
                String mergePropertyName = propertyName + "." + resolvePropertyName(field);
                bindBean(mergePropertyName, field.getType(), configProperties, propertyValues);
            }

        }
    }

    private static boolean containsDescendantOf(Set<String> names, String propertyName) {
        Iterator var2 = names.iterator();

        String name;
        do {
            if (!var2.hasNext()) {
                return false;
            }

            name = (String) var2.next();
        } while (!name.startsWith(propertyName + "."));

        return true;
    }

    private static boolean isUnbindableBean(Class<?> resolved) {
        return !resolved.isPrimitive() && !NON_BEAN_CLASSES.contains(resolved) ? resolved.getName().startsWith("java.") : true;
    }

    private static void bindContainer(String prefix, String fieldName, Map<String, Object> configProperties, MutablePropertyValues propertyValues) {
        String regx1 = fieldName + "\\[(.*)\\]";
        String regx2 = fieldName + "\\..*";
        Pattern pattern1 = Pattern.compile(regx1);
        Pattern pattern2 = Pattern.compile(regx2);
        Set<String> enumeration = configProperties.keySet();
        Iterator var9 = enumeration.iterator();

        while (var9.hasNext()) {
            Object item = var9.next();
            String s = String.valueOf(item);
            String name = StringUtils.isEmpty(prefix) ? s : s.replace(prefix + ".", "");
            Object value = configProperties.get(s);
            if (configProperties.containsKey(fieldName)) {
                bindContainer(prefix, fieldName, listToProperties(fieldName, String.valueOf(configProperties.get(fieldName))), propertyValues);
            } else if (pattern1.matcher(s).find()) {
                propertyValues.add(name, value);
            } else if (pattern2.matcher(s).find()) {
                int index = s.indexOf(46);
                if (index != -1) {
                    String key = s.substring(index + 1);
                    propertyValues.add(s.substring(0, index) + "[" + key + "]", value);
                }
            }
        }

    }

    private static Map<String, Object> listToProperties(String fieldName, String content) {
        String[] splits = content.split(",");
        int index = 0;
        Map<String, Object> properties = new LinkedHashMap();
        String[] var5 = splits;
        int var6 = splits.length;

        for (int var7 = 0; var7 < var6; ++var7) {
            String s = var5[var7];
            properties.put(fieldName + "[" + index + "]", s.trim());
            ++index;
        }

        return properties;
    }

    private static String resolvePropertyName(Field field) {
        if (AnnotationUtils.getAnnotation(field, NacosIgnore.class) != null) {
            return null;
        } else {
            NacosProperty nacosProperty = (NacosProperty) AnnotationUtils.getAnnotation(field, NacosProperty.class);
            return nacosProperty != null ? nacosProperty.value() : field.getName();
        }
    }

    public static <T> Class<T> resolveGenericType(Class<?> declaredClass) {
        ParameterizedType parameterizedType = (ParameterizedType) declaredClass.getGenericSuperclass();
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        return (Class) actualTypeArguments[0];
    }

    public static Map<String, Object> toProperties(String text) {
        return toProperties(text, "properties");
    }

    public static Map<String, Object> toProperties(String text, String type) {
        return toProperties("", "", text, type);
    }

    public static Map<String, Object> toProperties(String dataId, String group, String text) {
        return toProperties(dataId, group, text, "properties");
    }

    public static Map<String, Object> toProperties(String dataId, String group, String text, String type) {
        type = type.toLowerCase();
        if ("yml".equalsIgnoreCase(type)) {
            type = "yaml";
        }

        return ConfigParseUtils.toProperties(dataId, group, text, type);
    }
}